/**************************************************************************************
* Actor.java
* 
* Might
-Weapons have a brawn requirement to use effectively with one or both hands. If your brawn is lower than the two-hands requirement, or lower than the one-hand requirement and you use a shield or talisman, you will suffer a 20% attack1 penalty for each point of shortfall.
-Brawn is required to use weapons and armor, and high brawn both allows the use of heavier/stronger weapons and armor without penalty and adds a bonus to gear whose requirements you exceed (limited to the stat requirement, so a weapon requiring 4 brawn would allow a bonus of 4 at most).
-Max mass you can carry without being encumbered. Being Encumbered means you cannot jump or swim, your speed is reduced, and your stealth is penalized. Also your defense value from Grace is zero.
-Throwing range (2 + brawn/mass of thrown item). Throwing uses the average of Brawn and Grace for requirements, so it increases your thowing attack1 power as well.
-Max Health. Your maximum health is equal to your role's life rating + brawn * size mod
-Recovery from being "Stuck". Stickiness status (being unable to move) is reduced by 1+brawn/2 every turn.

Will
-Number of castings when you study a spell book (books have a min will requirement, casting under-willed results in backlash, excess will grants additional castings). The number of different spells you can have studied at a time is based on role and guild.
-Boosts Resolve(save verses spell effects), Leadership (boost to allies' stats), and Lore (ritual magic, alchemy, and the use of wands/scrolls)

Skill
-Explore skill based on Will (explore provides chance at partial mapping, stair/door detection, item and monster detection when you enter a new level)
-Ranged Weapons have grace requirements to use effectively and high grace allows you to shoot while on the move.
-Boosts mobility, swimming, stealth, Perception, and Tech (use gadgets and terminals) abilities
-Adds to your defense if you are not encumbered.
*************************************************************************************/
package creid.mythos.engine;

import java.io.Serializable;
import java.util.LinkedList;

public class Actor extends Entity implements Serializable
{

	//Constants/////////////////////////////////////////////////////////////////////////
	
	private static final long serialVersionUID = -5691727562587541063L;
	
	public static final int DEAD = -1;
	public static final int IMMOBILE = 0;
	public static final int SLOW = 1;
	public static final int AVERAGE = 2;
	public static final int FAST = 3;
	public static final int VERY_FAST = 4;
	
	public static final int PLAYER_FACTION = 0;
	public static final int MONSTER_FACTION = 1;
	
	//Number of turns mob will be aware after seeing a player
	//Could be mob-specific to differentiate trackers and "lazy" monsters.
	public static final int MELEE_RANGE = 0; 
	public static final int FLEE_THRESHOLD = 20; 
	public static final int FOLLOW_RANGE = 10; 
	
	//Size constants
	public static final int SMALL = 0;
	public static final int MEDIUM = 1;
	public static final int LARGE = 2;
	public static final int SIZE_MOD_DIVISOR = 4;
	
	public static final int DEFENSE_ABSORPTION_DIV = 2;
	
	//Helth multiplier
	protected static final int[] HEALTH_MOD = {3, 4, 6};
	
	//Attributes////////////////////////////////////////////////////////////////////////
	
	//Game Attributes
	protected int brawn;
	protected int will;
	protected int grace;
	
	//Faction
	protected int faction;
	
	protected int size;
	
	//movement speeds
	protected int speed;
	
	protected int armor;
	
	//Inventory
	private LinkedList<Item> inventory;
	
	//move speed modifier
	private boolean secondMove;
	
	//NEW STATS///////////////////////////////////////////
	
	//New AI stuff
	//chance of shooting - should these be combined into chance of special? maybe
	//chance of spell
	
	//Movement flags
	//walk, swim, fly
	protected boolean walks;
	protected boolean flies;
	protected boolean swims;
	
	//private int will;	//magic/status resist
	//private int[] resistances;	//Elemental defense/resistance.

	protected int vigilance;	//eyesight
	//private int darksight;	//dark-based eyesight (light and dark effects reversed)
	//private int ESP;		//auto-detection range, even through walls. ALso sees invisible
	
	//private int lightRadius;	//lights area around him. Can be negative for Unlight/Shadow
	
	protected Item naturalWeapon1;
	protected Item naturalWeapon2;
	
	protected Item attack1;	//Primary melee attack1, used 2/3 of the time
	protected Item attack2;	//secondary attack1, used 1/3 of the time
	
	//Shooting?
	
	//Magic
	
	//HP/resources//////////////////////
	protected int health;
	protected int maxHealth;
	
	//Defense///////////////////////////
	protected int attack; 
	protected int defense;
	protected int willpower;
	
	//Utility///////////////////////////
	
		//Location
	private Cell location;
	
	//AI data
	//How we path
	private int pathType;
	
	//How aware are we of the enemy (do we see one or did we see one recently
	private int awareness;
	
	//How close (or far away) from the enemy do we want to get
	private int preferredRange;

	//Constructors//////////////////////////////////////////////////////////////////////

	public Actor(int id, String singular, String plural, char[] sprite, int level, int size, int rarity,
			//AI stuff
			int faction, int pathType, int preferredRange, int vigilance,
			//Mobility
			boolean walks, boolean swims, boolean flies, int speed,
			//Combat stats
			int armor, int brawn, int grace, int will, Item attack, Item attack2)
	{
		super(id, singular, plural, sprite, level, rarity);
		
		this.size = size;
		secondMove = false;
		this.faction = faction;
		this.pathType = pathType;
		this.preferredRange = preferredRange;
		awareness = 0; 
		this.walks = walks;
		this.flies = flies;
		this.swims = swims;
		this.speed = speed;
		this.vigilance = vigilance;
		this.armor = armor;
		naturalWeapon1 = attack;
		naturalWeapon2 = attack2;
		
		if (naturalWeapon1 != null)
			this.attack1 = new Item(naturalWeapon1);
		else
			attack1 = null;
		
		if (naturalWeapon2 == null)
			this.attack2 = null;
		else
			this.attack2 = new Item(naturalWeapon2);
		
		this.brawn = brawn;
		this.grace = grace;
		this.will = will;
		inventory = new LinkedList<Item>();
		//TODO: Allow monsters to start with items to drop when killed or have stolen
		calcStats();
		health = maxHealth;
	}
	
	public Actor(Actor actor)
	{
		super(actor);
		
		level = actor.level;
		size = actor.size;
		secondMove = false;
		this.faction = actor.faction;
		this.pathType = actor.pathType;
		this.preferredRange = actor.preferredRange;
		awareness = 0; 
		this.walks = actor.walks;
		this.flies = actor.flies;
		this.swims = actor.swims;
		this.speed = actor.speed;
		this.vigilance = actor.vigilance;
		this.armor = actor.armor;
		
		if (actor.naturalWeapon1 == null)
			this.naturalWeapon1 = null;
		else
			this.naturalWeapon1 = new Item(actor.naturalWeapon1);
		
		if (actor.naturalWeapon2 == null)
			this.naturalWeapon2 = null;
		else
			this.naturalWeapon2 = new Item(actor.naturalWeapon2);
		
		if (naturalWeapon1 == null)
			attack1 = null;
		else
			attack1 = new Item(naturalWeapon1);
		
		if (naturalWeapon2 == null)
			attack2 = null;
		else
			attack2 = new Item(naturalWeapon2);

		this.defense = actor.defense;
		this.willpower = actor.willpower;
		inventory = new LinkedList<Item>();
		
		//TODO: Allow monsters to start with items to drop when killed or have stolen
		
		//Primary stats
		this.brawn = actor.brawn;
		this.will = actor.will;
		this.grace = actor.grace;	

		
		//Generate some items
		//TODO: Restrict to flags
		if (hasProperty("drop"))
		{
			//The way drop works is that the ones digit is the chance in 10 of dropping something
			//and the tens column is the number of times to check, so 34 means 3 chances to generate
			//items, each at 40%.
			int drop = getProperty("drop");
			int num = drop / 10;
			drop %= 10;
			if (num == 0)
				num = 1;
			
			for(; num > 0; num--)
				if (Mythos.callRNG(9) <= drop)
					stowItem(Mythos.randomItem(level));
		}
		
		//Finish out attributes
		calcStats();
		health = maxHealth;
	}

	//Methods///////////////////////////////////////////////////////////////////////////
	public int getSpeed()
	{
		return speed;
	}
	
	boolean canWalk()
	{
		return walks;
	}
	
	boolean canSwim()
	{
		return swims;
	}
	
	boolean canFly()
	{
		return flies;
	}

	public Cell getLocation()
	{
		return location;
	}

	public void setLocation(Cell location)
	{
		this.location = location;
	}
	
	public int getSize()
	{
		return size;
	}

	public void setSize(int size)
	{
		this.size = size;
		calcStats();
	}

	public void setVision(int vision)
	{
		this.vigilance = vision;
	}

	public int getAwareness()
	{
		return awareness;
	}
	
	public int getDefense(int damageType)
	{
		switch(damageType)
		{
		case Entity.MENTAL:
			return will;
		default:
			return grace;
		}
	}
	
	public void setAwareness(int awareness)
	{
		this.awareness = awareness;
	}

	public int getPreferredRange()
	{
		return preferredRange;
	}

	public void setPreferredRange(int preferredRange)
	{
		this.preferredRange = preferredRange;
	}

	public void setSpeed(int speed)
	{
		this.speed = speed;
	}
	
	/**
	 * Check if the actor should get a turn or not
	 * @return
	 */
	public boolean isDead()
	{
		return (health < 1);
	}
	
	/**
	 * AI chooses action for creature and takes its turn
	 */
	public void takeTurn()
	{
		//Do we move/melee? Assume yes unless we decide to do something else, like shoot.
		Cell destination = getLocation(); //set this to null to not move
		
		//TODO: Flee at low health, at least some of the time
		
		//Do we see an enemy?
		Actor foe = getLocation().getMap().closestHostile(this);
		
		if (foe != null)
		{
			int oldAware = awareness;
			awareness += vigilance;
			if (awareness > vigilance)
				awareness = vigilance;
			
			//Warn if a monster is waking up
			if (oldAware <= 0 && awareness > 0 && getLocation().isSeen())
				Mythos.logger.logMessage(getName(Entity.DEFINITE) + " spots you!");
		}
		//Lose interest over time
		else if (awareness > 0)
			awareness --;
		
		//Hunt the enemy
		if (awareness > 0)
		{
			//Are we at critical health? Or aAre we too close? then back off
			boolean fleeing = (maxHealth/FLEE_THRESHOLD >= health || preferredRange > getLocation().getDMap(pathType));			
			destination = getLocation().getMap().getPath(faction, pathType, getLocation(), fleeing);
		}
		else //Hold on or wander
		{
			destination = getLocation();
			
			//Have pets follow the player
			if (faction == PLAYER_FACTION)
			{
				int followDist = FOLLOW_RANGE;
				
				//Check our pathing to the player - ally path maps are land mob maps +1
				if (followDist < getLocation().getDMap(GameMap.PC_PATH))
				{
					destination = getLocation().getMap().getPath(faction, GameMap.PC_PATH, getLocation(), false);
				}
			}
		}
		
		//If we have a destination, go there
		if (destination != null)
			move(destination);
	}
	public int getBrawn()
	{
		return brawn;
	}

	public void setBrawn(int brawn)
	{
		this.brawn = brawn;
	}

	public int getWill()
	{
		return will;
	}

	public void setWill(int will)
	{
		this.will = will;
	}

	public int getGrace()
	{
		return grace;
	}

	public void setGrace(int grace)
	{
		this.grace = grace;
	}

	public int getFaction()
	{
		return faction;
	}

	public void setFaction(int faction)
	{
		this.faction = faction;
	}
	
	public boolean canDoubleMove()
	{
		return secondMove;
	}

	public void setDoubleMove()
	{
		secondMove = false;
	}
	
	public int getHealth()
	{
		return health;
	}

	public void setHealth(int health)
	{
		this.health = health;
	}

	public int getMaxHealth()
	{
		return maxHealth;
	}

	public void setMaxHealth(int maxHealth)
	{
		this.maxHealth = maxHealth;
	}
	
	public void die(String source)
	{
		//Say something about it
		if (getLocation().isSeen())
			Mythos.logger.logMessage(getName(Entity.DEFINITE) + " " + source);
		
		//Drop any carried items
		while(!inventory.isEmpty())
		{
			getLocation().getMap().placeItem(inventory.removeFirst(), getLocation().getX(), getLocation().getY(), true);
		}
		
		//Pull from the level
		getLocation().getMap().removeActor(this);
		
		//Did we kill the boss?
		if (hasProperty("guardian"))
		{
			Mythos.logger.logMessage("You have conquered this dungeon!");
		}
		
		//TODO: Better win condition than "killed the guardian"
		if (hasProperty("finalboss"))
		{
			Mythos.winner = true;
			Mythos.logger.logMessage("Congratulations!");
			Mythos.logger.logMessage("You are now a winner! Enter \"Q\" to quit at any time to retire in triumph.");
			Mythos.logger.logMessage("Beware - you can still lose if you are killed before you retire!");
		}
	}
	
	public void heal(int amount)
	{
		health += amount;
		
		if (health >= maxHealth)
		{
			health = maxHealth;
			if (getLocation().isSeen())
			{
				Mythos.logger.logMessage(getName(DEFINITE) + " is completely healed for " + amount + " health");
			}
		}
		else if (getLocation().isSeen())
			Mythos.logger.logMessage(getName(DEFINITE) + " is healed for " + amount + " health");

	}
	
	public void takeHit(String source, int damage)
	{
		health -= damage;
		
		if (isDead())
			die(source);
	}
		
	/**
	 * isFriendly
	 * Checks if the actor is friendly to us
	 */
	
	public boolean isFriendly(Actor other)
	{
		//TODO: Allow friendly/neutral factions, and traitor status
		return (getFaction() == other.getFaction());
	}
	
	/**
	 * isEncumbered
	 * check if actor is weighed down by equipment
	 * @return
	 */
	public boolean isEncumbered()
	{
		if (hasProperty("lumbering") || hasProperty("immobilized"))
			return true;
		else
			return false;
	}
	
	/**
	 * actionNames:
	 * Set the names to print in messages. Updates string arguments
	 * @param myName
	 * @param otherGuyName
	 * @return name of otherGuy
	 */
	private String actionName()
	{
		if (getLocation().isSeen())
			return getName(Entity.DEFINITE);
		else
			return "Something";
	}

	private String actionName(Actor otherGuy)
	{
		if (otherGuy.getLocation().isSeen())
			return otherGuy.getName(Entity.DEFINITE);
		else
			return "Something";		
	}
	
	boolean canOperate()
	{
		if (hasProperty("operate"))
			return true;
		else
			return false;
	}
	
	private String actionName(Cell place)
	{
		if (place.isSeen())
			return place.getName(Entity.DEFINITE);
		else
			return "something";		
	}

	/**
	 * Move
	 * move the actor to a location
	 * If the location is not passable but is operable (door, lever, computer) then operate it instead
	 * If the location is occupied, if its an enemy change to a melee attack1
	 * But if it is friendly and we are the PC, swap places with it, or if the occupant is not the PC and we are stronger then swap places
	 * Otherwise stop for friendlies. 
	 * Some actors may be unswappable (immobile or massive)
	 * If the occupant is neutral to us, do nothing, unless we are berserk, crazy, or otherwise wicked.
	 * If the player, make them confirm before melee attacking them.
	 * 
	 * @param destination
	 * @return success or cancelled
	 */
	public boolean move(Cell destination)
	{
		boolean seen = false;
		String aName = actionName();
		String tName = actionName(destination);
		
		boolean success = true;
		
		if(getLocation().isSeen() || destination.isSeen())
			seen = true;
		
		//sanity check: can we enter the cell?
		if (!destination.isPassable(walks, swims, flies))
		{
			//See if the terrain can be operated to allow movement
			if (canOperate() && destination.getOperation() != Cell.NO_OP && 
					Mythos.TerrainTypes[destination.getOperation()].isPassable(walks, swims, flies))
			{
				success = true;
				return operateTerrain(destination); //operate the terrain instead
			}
			success = false;
		}
		
		//Is the destination already occupied? And don't count ourselves if we are staying put (moving into the same cell)
		if (destination.isOccupied() && destination != getLocation())
		{
			success = false;
			if (destination.isSeen())
			{
				seen = true;
				tName = actionName(destination.getOccupant());
			}
			
			//Do we melee it? If so, don't say anything about it since meleeAttack will handle the message
			return meleeAttack(destination.getOccupant());
		}
		
		if (success)
		{
			//Fast means 50% chance to act after moving, very fast 100% chance. Only moving though, attacking ends your turn still
			if (speed == VERY_FAST || (speed == FAST && Mythos.callRNG(1, 2) == 1))
				secondMove = true;

			//Immobile creatures do nothing
			if (speed == IMMOBILE)
				return success;
			
			//If we are staying put, we're done. Otherwise, go there
			if (destination != getLocation())
			{
				//Slow creatures sometimes don't move
				if (speed == SLOW && Mythos.callRNG(1,2) == SLOW)
					return success;
				
				if (destination.setOccupant(this))
				{
					getLocation().setOccupant(null);
					setLocation(destination);
				}
				else
				{
					success = false;
					Mythos.logger.logMessage("Error: failed to move for some reason!");
				}
			}
		}
		else
		{
			if (seen)
				Mythos.logger.logMessage(aName + " stops to avoid hitting " + tName);
		}
		
		return success;
	}
	
	protected Item getWeapon()
	{
		Item weapon = attack1;
		if (weapon == null || (attack2 != null && Mythos.callRNG(2) == 0))
			weapon = attack2;
		
		return weapon;
	}
	
	private int calcAttackPower(Item attack, Actor target)
	{
		//basically just attack.power unless there are modifications for the target (resistances, slays, etc)
		int ap = attack.getPower();
		
		return ap;
	}
	
	/**
	 * meleeAttack
	 * 
	 * Make a melee attack1 against the occupant of the target cell
	 * This is an opposed roll of Attack vs Defense, each +- up to 50%.
	 * Power = Value/2 + (0-Value/4) + (0-Value/4).
	 * To avoid losing fractional numbers from integer division consider the modulo of the value and add accordingly:
	 * Power %4:
	 * 0-Example 12. No loss of info from /4 or /2
	 * 1-Example 9. F/2 = 4, F/4=2, lose .5 from one and .25 from each F/4, totalling .5+.25+.25 = 1. So add 1 to the result
	 * 2-Example 10. F/2 = 5, F/4 = 2. Lose .25 from each F/4, totalling 0+.25+.25 = 0.5. So add 1 50% of the time
	 * 3-Example 11. F/2 = 5(-.5), F/4 = 2(-.75), total = .5+.75+.75 = 2. So add 2 to the result.
	 * 
	 * Damage is not random - it is based on Attack and is proportional to the Attack:Defense ratio
	 * This allows a look at a monster to give combat odds as "You have a X% chance to hit this monster, and can defeat it in Y hits (average P turns). It has a 
	 * Z% chance to hit you, and could defeat you in W hits (average Q turns).
	 * So a player can see whether he can expect to kill a monster in melee or not. This gives one random factor (do X damage or do none at all) and should help the 
	 * player be informed on the correctness of attacking a monster or not. 
	 * @param target - actor the attack1 is made against
	 * @return whether a turn was taken
	 */
	public boolean meleeAttack(Actor target)
	{
		boolean success = true;
		
		String aName = actionName();
		String tName = actionName(target);
		
		//Is the target friendly?
		if (isFriendly(target) || (attack1 == null && attack2 == null))
		{
			success = false;	//Don't attack1 friendlies
			Mythos.logger.logMessage(aName + " stops to avoid hitting " + tName);
		}
		else //Kill him, Daddy!
		{
			//TODO: Evasion, % chance to stop a melee attack1.
			//TODO: Invisible targets are hard to hit, give them a 50% save
			
			Item weapon = getWeapon();
			
			//If no weapon, no attack
			if (weapon == null)
				return false;

			StringBuilder attackMsg = new StringBuilder(aName + " " + weapon.getUseVerb() + " " + tName + " ");
			
			//Get Attack power
			//TODO: Allow terrain to modify power - i.e. make standing in water cause electricity to be more powerful, or runes of protection weakening it
			
			int attack = calcAttackPower(weapon, target);
			
			int defense = target.getDefense(weapon.getElement());
			
			//Check to see if the attack hits and causes damage (roll > 0)
			int roll = Mythos.callRNG(1, attack + defense);
			System.out.println("Rolling " + attack + " vs " + defense + "; result = " + roll); 
			
			if (roll <= attack)
			{
				//Defense reduces damage by 1/2.
				roll -= (defense/DEFENSE_ABSORPTION_DIV);
				
				//Always do at least 1 damage if we hit
				if (roll < 1)
					roll = 1;
			}
			else //Fail
				roll = 0;
			
			//Print a message if we see the attack1
			if (getLocation().isSeen() || target.getLocation().isSeen())
			{
				if (roll > 0)
				{
					attackMsg.append("for " + roll + " damage!");
				}
				else
				{
					attackMsg.append("but does no damage!");
				}
				Mythos.logger.logMessage(attackMsg.toString());
			}
			
			//Now apply the damage (do it at the end so that we don't print about a creature's death before its being attacked
			if (roll > 0)
			{
				String source = "was killed by " + aName;
				
				//Hackish - let player know true name of what killed him
				if (this instanceof Player)
					source = "was killed by " + getName(Entity.INDEFINITE);
				
				target.takeHit(source, roll);
			}
		}
		
		return success;
	}
	
	boolean operateTerrain(Cell targetCell)
	{		
		if (targetCell == null)
		{
			Mythos.logger.errorLog("operateTerrain: Received null target cell!");
			return false;
		}

		String aName = actionName();
		String tName = actionName(targetCell);
		boolean seen = false;
		if (getLocation().isSeen() || targetCell.isSeen())
			seen = true;
		
		
		if (targetCell.getOperation() == Cell.NO_OP)
		{
			if (seen)
				Mythos.logger.logTransientMessage(aName + "looks, but sees nothing to operate there");
			return false;
		}
		
		String opMsg = targetCell.getOpMessage();
		if (targetCell.operate(1))
		{
			if (seen)
				Mythos.logger.logMessage(aName + " " + opMsg);
			return true;
		}
		else
		{
			if (seen)
				Mythos.logger.logMessage(aName + " fumbles with " + tName);
			return false;
		}
	}
	
	void calcHealth()
	{
		//Being big or small alters health
		int healthMod = level;
		if (size == MEDIUM)
			healthMod *= 2;
		else if (size == LARGE)
			healthMod *= 3;
		
		healthMod /= 2;
		
		maxHealth = 2*brawn + healthMod;
		
		//Elites get a health bonus
		if (hasProperty("Elite"))
			maxHealth = (maxHealth * 3)/2;
		
		//Minimum max health
		if (maxHealth < 1)
			maxHealth = 1;
 
		//Max health is max
		if (health > maxHealth)
			health = maxHealth;
	}
	
	void calcAttacks()
	{
		if (naturalWeapon1 != null)
		{
			attack1.setElement(naturalWeapon1.getElement());
			
			int power = naturalWeapon1.getPower();
			power += brawn - power;
			if (power < 1)
				power = 1;
			
			attack1.setPower(power);
		}
		else
			attack1 = null;
		
		if (naturalWeapon2 != null)
		{
			attack2.setElement(naturalWeapon2.getElement());
			
			int power = naturalWeapon2.getPower();
			power += brawn - power;
			if (power < 1)
				power = 1;
			
			attack2.setPower(power);
		}
		else
			attack2 = null;
	}
	
	void calcDefenses()
	{
		defense = grace + armor;
		willpower = will;
	}
	
	void calcStats()
	{
		//---------------Primary Stats--------------------------------------------
		
		//--------------------HEALTH----------------------------------------------
		calcHealth();
		
		//--------------------ATTACK----------------------------------------------
		calcAttacks();
		
		//------Defenses----------------------------------------------------------
		calcDefenses();
	}
	
	//Pick up item
	public boolean pickupItem()
	{
		if (getLocation().hasTreasure())
		{
			//Take the item
			Item item = getLocation().takeTreasure();
			stowItem(item);
			
			//See it
			if (getLocation().isSeen())
			{
				Mythos.logger.logMessage(getName(DEFINITE) + " picks up " + item.getName(DEFINITE));
			}
			
			return true;
		}
		else //Nothing to pick up
			return false;
	}
	
	public Item[] listInventory()
	{
		return inventory.toArray(new Item[inventory.size()]);
	}
	
	/**
	 * stowItem
	 * put item in inventory
	 * @param i
	 * @return
	 */
	public void stowItem(Item item)
	{
		//If there is a duplicate item, stack them
		int duplicate = -1;
		for (int i = 0; i < inventory.size(); i++)
		{
			if (inventory.get(i).compareTo(item) == 0)
			{
				duplicate = i;
				break;
			}
		}
		
		if (duplicate == -1)
		{
			boolean last = true;
			for (int i = 0; i < inventory.size(); i++)
			{
				//Compare types, if types are the same, check spelling
				if (item.getType() < inventory.get(i).getType() || 
						(item.getType() == inventory.get(i).getType() && 
						(item.getSingularName().compareTo(inventory.get(i).getSingularName()) < 0)))
				{
					inventory.add(i, item);
					last = false;
					break;
				}
			}
			
			if (last)
				inventory.addLast(item);
		}
		else //combine
			inventory.get(duplicate).setAmount(inventory.get(duplicate).getAmount() + item.getAmount());
		
		//TODO: Sort inventory by item type and name
	}
	
	public Item takeItem(int index, boolean all)
	{
		if (all || inventory.get(index).getAmount() == 1)
			return inventory.remove(index);
			
		Item temp = new Item(inventory.get(index));
		temp.setAmount(1);
		
		inventory.get(index).setAmount(inventory.get(index).getAmount() - 1);
		
		return temp;
	}
	
	public Item seeItem(int index)
	{
		return inventory.get(index);
	}
	
	boolean dropItem(int index)
	{
		Item item = inventory.get(index);
		boolean success = false;
		
		if (getLocation().getMap().placeItem(item, getLocation().getX(), getLocation().getY(), true))
		{
			success = true;
			inventory.remove(index);
			
			if (getLocation().isSeen())
				Mythos.logger.logMessage(getName(DEFINITE) + " drops " + item.getName(DEFINITE));
		}
		
		return success;
	}
	
	/**
	 * throwItem
	 * Make a ranged attack using an item from your inventory.
	 * @param whichItem: inventory index to pull item from. If there is a stack of items only one will
	 * be thrown.
	 * TODO: An ability to throw multiple items in a stack at once?
	 * @param target cell to attack
	 * @return whether the action was completed or not (i.e. whether the actor used their turn).
	 */
	boolean throwItem(int whichItem, Cell target)
	{
		//Get the item to throw
		if (whichItem < 0 || whichItem >= inventory.size())
		{
			Mythos.logger.errorLog("Actor.throwItem() called with invalid inventory index " + whichItem + ", valid is 0-" + (inventory.size() - 1));
			return false;
		}
		
		//Verify the target
		if (target == null || target.getMap() != getLocation().getMap())
		{
			Mythos.logger.errorLog("Actor.throwItem(): Null or off-map cell targeted, aborting.");
			return false;
		}
		
		Item thrownItem = takeItem(whichItem, false);

		//If item doesn't have a throw damage, assign one
		if (!thrownItem.hasProperty("throw"))
			thrownItem.setProperty("throw", 1);

		//If item doesn't have a land chance, assign it one
		if (!thrownItem.hasProperty("land"))
			thrownItem.setProperty("land", 4);
		
		//Some bonus damage from extra brawn
		int damageMod = thrownItem.getPower() - brawn;
		if (damageMod > 0)
			damageMod /= 2;
		
		if (thrownItem.getPower() < brawn)
		{
			int damage = Math.max(1, thrownItem.getProperty("throw") + damageMod);
			thrownItem.setProperty("throw", damage);
		}

		//Create a projection
		String targetName = "";
		if (target.isOccupied())
			targetName = "at " + target.getOccupant().actionName();
		
		Projection p = Projection.boltProjection(thrownItem, getLocation(), target, getName(DEFINITE) + " throws " + thrownItem.getThrowName() + targetName);
		
		//Throwing range capped at brawn
		if (brawn < p.getRange())
			p.setRange(brawn);
		
		//Upload projection to map
		getLocation().getMap().addProjection(p);
		
		//We did something
		return true;
	}
	
	boolean shoot(Item shot, Cell target)
	{
		//If projectile or target not provided, abort
		if (shot == null)
		{
			Mythos.logger.errorLog("Actor.shoot() called without a shot!");
			return false;
		}
		
		if (target == null)
		{
			Mythos.logger.errorLog("Actor.shoot() called without a target!");
			return false;
		}
		
		//Create a projection
		String targetName = "";
		if (target.isOccupied())
			targetName = "at " + target.getOccupant().actionName();

		Projection p = Projection.boltProjection(shot, getLocation(), target, getName(DEFINITE) + " shoots " + shot.getName(INDEFINITE) + targetName);
	
		//Short-ranged weapons don't shoot as far
		if (shot.hasProperty("shortrange"))
			p.setRange(p.getRange() /2);
		
		//Upload projection to map
		getLocation().getMap().addProjection(p);	
		
		return true;
	}
	
	boolean useItem(int index, Cell target)
	{
		if (index < 0 || index >= inventory.size())
		{
			Mythos.logger.errorLog("Actor.useItem() called with invalid item index '" + index + "'");
			return false;
		}
		
		if (inventory.get(index).getUse() == null)
		{
			Mythos.logger.errorLog("Actor.useItem() called on unusuable item");
			return false;
		}
		
		//Strip out projection data
		Item useEffect = inventory.get(index).getUse();
		int range = useEffect.getProperty("range");
		useEffect.setProperty("range", 0);
		int radius = useEffect.getProperty("radius");
		useEffect.setProperty("radius", 0);
		int projectionType = useEffect.getProperty("projectionType");
		useEffect.setProperty("projectionType", 0);
		
		//Create a projection for the item
		getLocation().getMap().addProjection(new Projection(useEffect, projectionType, getLocation(), target, radius, range, useEffect.getUseVerb()));
		
		//Consume the item if needed
		if (inventory.get(index).isConsumable())
			takeItem(index, false);
		
		return true;
	}
}